﻿using System;
using System.Collections.Generic;
using System.Data.SqlServerCe;
using System.Linq;
using SenseTimeTracker.BusinessLayer;
using SenseTimeTracker.Misc;
using SenseTimeTracker.Properties;

namespace SenseTimeTracker.Holidays
{
	/// <summary>
	/// This class implements calender functionalty
	/// </summary>
	public class Calendar
	{
		/// <summary>
		/// Distionary conraining the holidays
		/// </summary>
		private static Dictionary<DateTime, double> holidays = new Dictionary<DateTime,double>();

		/// <summary>
		/// Method to reset the holidays
		/// </summary>
		public static void ResetHolidays()
		{
			holidays = null;
		}

		/// <summary>
		/// Function to calculate the easter date for a given year.
		/// It uses the gaussian formula.
		/// </summary>
		/// <param name="year">
		/// The year to calculate the easter for.
		/// </param>
		/// <returns>
		/// The date of easter.
		/// </returns>
		public static DateTime Easter(int year)
		{
			if (year == 0)
			{
				year = DateTime.Now.Year;
			}

			int a = year % 19;
			int b = year / 100;
			int c = (8 * b + 13) / 25 - 2;
			int d = b - (year / 400) - 2;
			int e = (19 * (year % 19) + ((15 - c + d) % 30)) % 30;
			if (e == 28)
			{
				if (a > 10)
				{
					e = 27;
				}
			}
			else if (e == 29)
			{
				e = 28;
			}

			int f = (d + 6 * e + 2 * (year % 4) + 4 * (year % 7) + 6) % 7;

			DateTime retDate = new DateTime(year, 3, 1);
			return retDate.AddDays(e + f + 21);
		}

		/// <summary>
		/// </summary>
		/// <param name="startDate">
		/// The start date.
		/// </param>
		/// <returns>
		/// </returns>
		public static Dictionary<DateTime, double> Estimation(DateTime startDate)
		{
			DateTime finalDate = startDate.AddMonths(1);
			Dictionary<DateTime, double> estimations = new Dictionary<DateTime, double>();
			var workingTimes = from workingTime in WorkingTime.WorkingTimes where workingTime.PersonId == Persistance.Instance.UserId orderby workingTime.ValidFrom ascending select workingTime;

			while (startDate < finalDate)
			{
				TimeSpan workingTime = Persistance.Instance.DefaultDailyWorkingTime;
				foreach (WorkingTime dailyWorkingTime in workingTimes)
				{
					if (dailyWorkingTime.ValidFrom <= startDate)
					{
						workingTime = dailyWorkingTime.DailyWorkingTime;
					}
					else
					{
						break;
					}
				}

				if (startDate.DayOfWeek != DayOfWeek.Saturday && startDate.DayOfWeek != DayOfWeek.Sunday)
				{
					if (IsHoliday(startDate))
					{
						estimations.Add(startDate.Date, workingTime.TotalHours * (1.0 - holidays[startDate] / 100.0));
					}
					else
					{
						estimations.Add(startDate.Date, workingTime.TotalHours);
					}
				}

				startDate += TimeSpan.FromDays(1);
			}

			return estimations;
		}

		private static bool IsHoliday(DateTime date)
		{
			if (holidays.Count > 0 && date.Year != holidays.Keys.ElementAt(0).Year)
			{
				holidays.Clear();
			}

			if (holidays.Count == 0)
			{
				SqlCeCommand command = new SqlCeCommand(
					"SELECT holidayId, enabled, type, day, month, dayofweek, count, utilisation " +
					"FROM Holidays WHERE personId=@personId",
					DatabaseManager.DbConnection);
				command.Parameters.Add("personId", Persistance.Instance.UserId);
				bool opened = DatabaseManager.DbConnection.State == System.Data.ConnectionState.Open;
				if (!opened)
				{
					DatabaseManager.DbConnection.Open();
				}

				SqlCeDataReader reader = command.ExecuteReader();
				while (reader.Read())
				{
					if ((bool)reader["enabled"])
					{
						DateTime holiday = Holiday.Date(date.Year, (int)reader["month"], (int)reader["day"], (DayOfWeek)reader["dayofweek"], (int)reader["count"], (Calendar.Holiday.HolidayType)reader["type"]);
						holidays[holiday] = (int)reader["utilisation"];
					}
				}

				if (!opened)
				{
					DatabaseManager.DbConnection.Close();
				}
			}

			return holidays.ContainsKey(date);
		}

		public static Holiday[] DefaultHolidays
		{
			get
			{
				return new[]
				{
					new Holiday { Name = "HolidayNewYearsDay", Day = 1, Month = 1, Type = Holiday.HolidayType.FixedDate, Utilisation = 100, Region = (int)Holiday.Regions.All },
					new Holiday { Name = "HolidayThreeMagisday", Day = 6, Month = 1, Type = Holiday.HolidayType.FixedDate, Utilisation = 100, Region = (int)Holiday.Regions.Bayern | (int)Holiday.Regions.BadenWürtemberg | (int)Holiday.Regions.SachsenAnhalt },
					new Holiday { Name = "HolidayValantinesDay", Day = 14, Month = 2, Type = Holiday.HolidayType.FixedDate, Utilisation = 0, Region = (int)Holiday.Regions.All },
					new Holiday { Name = "HolidayShroveMonday", Type = Holiday.HolidayType.EasterRelated, DayOfWeek = DayOfWeek.Monday, Count = -7, Utilisation = 0, Region = (int)Holiday.Regions.NordrheinWestfalen },
					new Holiday { Name = "HolidayShroveTuesday", Type = Holiday.HolidayType.EasterRelated, DayOfWeek = DayOfWeek.Tuesday, Count = -7, Utilisation = 0, Region = (int)Holiday.Regions.NordrheinWestfalen },
					new Holiday { Name = "HoldayGoodFriday", Type = Holiday.HolidayType.EasterRelated, DayOfWeek = DayOfWeek.Friday, Count = -1, Utilisation = 100, Region = (int)Holiday.Regions.All },
					new Holiday { Name = "HolidayEasterSunday", Type = Holiday.HolidayType.EasterRelated, DayOfWeek = DayOfWeek.Sunday, Count = 0, Utilisation = 100, Region = (int)Holiday.Regions.All },
					new Holiday { Name = "HolidayEasterMonday", Type = Holiday.HolidayType.EasterRelated, DayOfWeek = DayOfWeek.Monday, Count = 0, Utilisation = 100, Region = (int)Holiday.Regions.All },
					new Holiday { Name = "HolidayLabourDay", Day = 1, Month = 5, Type = Holiday.HolidayType.FixedDate, Utilisation = 100, Region = (int)Holiday.Regions.All },
					new Holiday { Name = "HolidayMothersDay", Type = Holiday.HolidayType.DateRelated, Day = 1, Month = 5, DayOfWeek = DayOfWeek.Sunday, Count = 1, Utilisation = 0, Region = (int)Holiday.Regions.All },
					new Holiday { Name = "HolidayAscensionDay", Type = Holiday.HolidayType.EasterRelated, DayOfWeek = DayOfWeek.Thursday, Count = 5, Utilisation = 100, Region = (int)Holiday.Regions.All },
					new Holiday { Name = "HolidayWhitSunday", Type = Holiday.HolidayType.EasterRelated, DayOfWeek = DayOfWeek.Sunday, Count = 7, Utilisation = 100, Region = (int)Holiday.Regions.All },
					new Holiday { Name = "HolidayWhitMonday", Type = Holiday.HolidayType.EasterRelated, DayOfWeek = DayOfWeek.Monday, Count = 7, Utilisation = 100, Region = (int)Holiday.Regions.All },
					new Holiday { Name = "HolidayCorpusChristi", Type = Holiday.HolidayType.EasterRelated, DayOfWeek = DayOfWeek.Thursday, Count = 8, Utilisation = 100, Region = (int)Holiday.Regions.BadenWürtemberg | (int)Holiday.Regions.Bayern | (int)Holiday.Regions.Hessen | (int)Holiday.Regions.NordrheinWestfalen | (int)Holiday.Regions.RheinlandPfalz | (int)Holiday.Regions.Saarland | (int)Holiday.Regions.Sachsen | (int)Holiday.Regions.Thueringen },
					new Holiday { Name = "HolidayPeaceDay", Day = 8, Month = 8, Type = Holiday.HolidayType.FixedDate, Utilisation = 0, Region = (int)Holiday.Regions.Bayern },
					new Holiday { Name = "HolidayMariaAscensionDay", Day = 15, Month = 8, Type = Holiday.HolidayType.FixedDate, Utilisation = 100, Region = (int)Holiday.Regions.Bayern | (int)Holiday.Regions.Saarland },
					new Holiday { Name = "HolidayThanksgiving", Type = Holiday.HolidayType.DateRelated, Day = 1, Month = 10, DayOfWeek = DayOfWeek.Sunday, Count = 1, Utilisation = 0, Region = (int)Holiday.Regions.All },
					new Holiday { Name = "HolidayGermanUnificationDay", Day = 3, Month = 10, Type = Holiday.HolidayType.FixedDate, Utilisation = 100, Region = (int)Holiday.Regions.All },
					new Holiday { Name = "HolidayReformationDay", Day = 31, Month = 10, Type = Holiday.HolidayType.FixedDate, Utilisation = 100, Region = (int)Holiday.Regions.Brandenburg | (int)Holiday.Regions.Mecklenburg | (int)Holiday.Regions.Sachsen | (int)Holiday.Regions.SachsenAnhalt | (int)Holiday.Regions.Thueringen },
					new Holiday { Name = "HolidayAllSaintsDay", Day = 1, Month = 11, Type = Holiday.HolidayType.FixedDate, Utilisation = 100, Region = (int)Holiday.Regions.BadenWürtemberg | (int)Holiday.Regions.Bayern | (int)Holiday.Regions.NordrheinWestfalen | (int)Holiday.Regions.RheinlandPfalz | (int)Holiday.Regions.Saarland },
					new Holiday { Name = "HolidayPenanceDay", Type = Holiday.HolidayType.DateRelated, Day = 24, Month = 12, DayOfWeek = DayOfWeek.Wednesday, Count = -6, Utilisation = 0, Region = (int)Holiday.Regions.Sachsen },
					new Holiday { Name = "HolidaySundayCommOfDead", Type = Holiday.HolidayType.DateRelated, Day = 24, Month = 12, DayOfWeek = DayOfWeek.Sunday, Count = -5, Utilisation = 0, Region = (int)Holiday.Regions.All },
					new Holiday { Name = "HolidayAdventFirst", Type = Holiday.HolidayType.DateRelated, Day = 24, Month = 12, DayOfWeek = DayOfWeek.Sunday, Count = -4, Utilisation = 0, Region = (int)Holiday.Regions.All },
					new Holiday { Name = "HolidayAdventSecond", Type = Holiday.HolidayType.DateRelated, Day = 24, Month = 12, DayOfWeek = DayOfWeek.Sunday, Count = -3, Utilisation = 0, Region = (int)Holiday.Regions.All },
					new Holiday { Name = "HolidayAdventThird", Type = Holiday.HolidayType.DateRelated, Day = 24, Month = 12, DayOfWeek = DayOfWeek.Sunday, Count = -2, Utilisation = 0, Region = (int)Holiday.Regions.All },
					new Holiday { Name = "HolidayAdventFourth", Type = Holiday.HolidayType.DateRelated, Day = 24, Month = 12, DayOfWeek = DayOfWeek.Sunday, Count = -1, Utilisation = 0, Region = (int)Holiday.Regions.All },
					new Holiday { Name = "HolidayChristmasEve", Day = 24, Month = 12, Type = Holiday.HolidayType.FixedDate, Utilisation = 50, Region = (int)Holiday.Regions.All },
					new Holiday { Name = "HolidayChritsmasDay", Day = 25, Month = 12, Type = Holiday.HolidayType.FixedDate, Utilisation = 100, Region = (int)Holiday.Regions.All },
					new Holiday { Name = "HolidayBoxingDay", Day = 26, Month = 12, Type = Holiday.HolidayType.FixedDate, Utilisation = 100, Region = (int)Holiday.Regions.All },
					new Holiday { Name = "HolidayNewYearsEve", Day = 31, Month = 12, Type = Holiday.HolidayType.FixedDate, Utilisation = 50, Region = (int)Holiday.Regions.All },
				};
			}
		}

		/// <summary>
		/// Class implementsa holiday structure
		/// </summary>
		public class Holiday : IColumnSortable
		{
			/// <summary>
			/// Field contains the day of month
			/// </summary>
			private int day = 1;

			/// <summary>
			/// Field contains the month of year
			/// </summary>
			private int month = 1;

			/// <summary>
			/// Field contains the holiday type
			/// </summary>
			private HolidayType holidayType = HolidayType.FixedDate;

			/// <summary>
			/// Field contains the day of the week
			/// </summary>
			private DayOfWeek dayOfWeek = DayOfWeek.Sunday;

			/// <summary>
			/// Field contains a repeater for weeks
			/// </summary>
			private int count;

			/// <summary>
			/// Field contains the name
			/// </summary>
			private string name = Resources.HolidayDefaultName;

			/// <summary>
			/// Field contains the utilisation in percent
			/// </summary>
			private double utilisation = 100;

			/// <summary>
			/// Field contains the region where the holiday is valid
			/// </summary>
			private int region;

			private bool enabled;
			private bool fixedHoliday;
			private Guid id;

			/// <summary>
			/// Enumeration for possible regions
			/// </summary>
			public enum Regions
			{
				BadenWürtemberg = 0x0001,
				Bayern = 0x0002,
				Berlin = 0x0004,
				Brandenburg = 0x0008,
				Bremen = 0x0010,
				Hamburg = 0x0020,
				Hessen = 0x0040,
				Mecklenburg = 0x0080,
				Niedersachsen = 0x0100,
				NordrheinWestfalen = 0x0200,
				RheinlandPfalz = 0x0400,
				Saarland = 0x0800,
				Sachsen = 0x1000,
				SachsenAnhalt = 0x2000,
				SchleswigHolstein = 0x4000,
				Thueringen = 0x8000,
				All = 0xffff,
			}

			/// <summary>
			/// Enumeration of possible holiday types
			/// </summary>
			public enum HolidayType
			{
				/// <summary>
				/// Date is fix
				/// </summary>
				[Description("HolidayFixedDate")]
				FixedDate,

				/// <summary>
				/// Date is related to another date but day of week dependent
				/// </summary>
				[Description("HolidayDateRelated")]
				DateRelated,

				/// <summary>
				/// Date is related to easter
				/// </summary>
				[Description("HolidayEasterRelated")]
				EasterRelated
			}

			/// <summary>
			/// Gets or sets the name.
			/// </summary>
			public string Name
			{
				get { return name; }
				set { name = value; }
			}

			public HolidayType Type
			{
				get { return holidayType; }
				set { holidayType = value; }
			}

			public int Day
			{
				get { return day; }
				set { day = value; }
			}

			public int Month
			{
				get { return month; }
				set { month = value; }
			}

			public DayOfWeek DayOfWeek
			{
				get { return dayOfWeek; }
				set { dayOfWeek = value; }
			}

			public int Count
			{
				get { return count; }
				set { count = value; }
			}

			public double Utilisation
			{
				get { return utilisation; }
				set { utilisation = value; }
			}

			public int Region
			{
				get { return region; }
				set { region = value; }
			}

			public bool Enabled
			{
				get { return enabled; }
				set { enabled = value; }
			}

			public bool Fixed
			{
				get { return fixedHoliday; }
				set { fixedHoliday = value; }
			}

			public Guid Id
			{
				get { return id; }
				set { id = value; }
			}

			public DateTime Date(int year)
			{
				return Date(year, month, day, dayOfWeek, count, holidayType);
			}

			public static DateTime Date(int year, int month, int day, DayOfWeek dayOfWeek, int count, HolidayType holidayType)
			{
				if (year == 0)
				{
					year = DateTime.Now.Year;
				}

				switch (holidayType)
				{
					case HolidayType.FixedDate:
						return new DateTime(year, month, day);
					case HolidayType.DateRelated:
						{
							DateTime date = new DateTime(year, month, day);
							int dayOffset = TimeSpan.FromDays(dayOfWeek - date.DayOfWeek).Days;
							if (dayOffset < 0)
							{
								dayOffset += 7;
							}

							return date + TimeSpan.FromDays(dayOffset + 7 * count);
						}

					case HolidayType.EasterRelated:
						return Easter(year) + TimeSpan.FromDays(dayOfWeek - DayOfWeek.Sunday + 7 * count);
				}

				return DateTime.MinValue;
			}

			public string DateFormatted
			{
				get { return Date(0).ToString(Resources.HolidayDisplayFormat); }
			}

			#region IColumnSortable Members


			string IColumnSortable.Type
			{
				get { throw new NotImplementedException(); }
			}

			public string Number
			{
				get { throw new NotImplementedException(); }
			}

			DateTime IColumnSortable.Date
			{
				get { return Date(DateTime.Now.Year); }
			}

			public string Description
			{
				get { throw new NotImplementedException(); }
			}

			public TimeSpan Duration
			{
				get { throw new NotImplementedException(); }
			}

			#endregion
		}
	}
}
